#include <stdio.h>
#include "Ntp_time.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "esp_sleep.h"
#include "nvs_flash.h"
#include "esp_netif_sntp.h"
#include "esp_netif.h"
#include "lwip/ip_addr.h"
#include "esp_sntp.h"
#include "esp_mac.h"
#include "PipeLine.h"

static const char* TAG = "NTP";

// 定义TimeInfo时间变量
TimeInfo time_info = {
    .year = 2025,
    .month = 2,
    .day = 25,
    .hour = 12,
    .minute = 10,
    .second = 15
};

#ifndef INET6_ADDRSTRLEN
#define INET6_ADDRSTRLEN 48
#endif
 
/* Variable holding number of times ESP32 restarted since first boot.
 * It is placed into RTC memory using RTC_DATA_ATTR and
 * maintains its value when ESP32 wakes from deep sleep.
 */
RTC_DATA_ATTR static int boot_count = 0;
 
static void obtain_time(void);
 
// 如果配置了使用自定义方法同步时间，则定义此函数
#ifdef CONFIG_SNTP_TIME_SYNC_METHOD_CUSTOM
void sntp_sync_time(struct timeval *tv)
{
   // 使用自定义的时间同步设置系统时间
   settimeofday(tv, NULL);
   // 记录日志，表明时间已通过自定义代码同步
   ESP_LOGI(TAG, "Time is synchronized from custom code");
   // 设置SNTP同步状态为已完成
   sntp_set_sync_status(SNTP_SYNC_STATUS_COMPLETED);
}
#endif
// 定义时间同步通知回调函数
void time_sync_notification_cb(struct timeval *tv)
{
    // 记录日志，表明发生了时间同步事件
    ESP_LOGI(TAG, "Notification of a time synchronization event");
}

// 将字符串时间转换为 TimeInfo 结构体
TimeInfo parse_time_str(const char *time_str) 
{
    struct tm tm_time;
    // 将tm结构体初始化为 0
    memset(&tm_time, 0, sizeof(struct tm));
    // 使用 strptime 解析时间字符串
    if (strptime(time_str, "%c", &tm_time) == NULL) 
    {
        ESP_LOGE("NTP", "Failed to parse time string: %s", time_str);
        return time_info; // 返回默认值 0
    }
    // 转换为局部时间（时区已由 setenv("TZ", "CST-8", 1) 设置）
    time_t now = mktime(&tm_time);
    localtime_r(&now, &tm_time);
    // 填充 TimeInfo 结构体
    time_info.year = tm_time.tm_year + 1900; // tm_year 是自 1900 年的偏移量
    time_info.month = tm_time.tm_mon + 1;    // tm_mon 是 0-11
    time_info.day = tm_time.tm_mday;
    time_info.hour = tm_time.tm_hour;
    time_info.minute = tm_time.tm_min;
    time_info.second = tm_time.tm_sec;

    return time_info;
}

void sntp_task(void* param)
{
    xEventGroupWaitBits(xEventGroup, WIFI_CONNECTED_BIT, pdTRUE, pdFALSE, portMAX_DELAY);
    printf("SNTP done\n");

    // 增加启动计数器
    ++boot_count;
    // 记录当前启动次数的日志信息
    ESP_LOGI(TAG, "Boot count: %d", boot_count);

    time_t now;  // 定义存储当前时间的变量
    struct tm timeinfo;  // 定义存储时间结构体的变量
    
    // 获取当前时间
    time(&now);
    // 将获取到的时间转换为本地时间，并存储到timeinfo结构体中
    localtime_r(&now, &timeinfo);

    // 检查时间是否已设置，如果没有设置，tm_year将等于1970年（1970 - 1900）
    if (timeinfo.tm_year < (2016 - 1900)) {
        // 记录时间未设置的日志信息，并准备连接WiFi以获取NTP时间
        ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP.");
        // 调用函数以获取当前时间
        obtain_time();
        // 更新当前时间
        time(&now);
    }
    
#ifdef CONFIG_SNTP_TIME_SYNC_METHOD_SMOOTH
    else {
        // 仅用于演示平滑同步方法的工作方式，向当前系统时间添加500毫秒的错误
        {
            ESP_LOGI(TAG, "Add a error for test adjtime");
            struct timeval tv_now;
            // 获取当前时间
            gettimeofday(&tv_now, NULL);
            // 计算当前CPU时间（以微秒为单位）
            int64_t cpu_time = (int64_t)tv_now.tv_sec * 1000000L + (int64_t)tv_now.tv_usec;
            // 计算添加了500毫秒错误的时间
            int64_t error_time = cpu_time + 500 * 1000L;
            // 将错误时间转换为timeval结构体
            struct timeval tv_error = { .tv_sec = error_time / 1000000L, .tv_usec = error_time % 1000000L };
            // 设置错误时间
            settimeofday(&tv_error, NULL);
        }

        // 记录时间已设置，现在仅进行调整的日志信息
        ESP_LOGI(TAG, "Time was set, now just adjusting it. Use SMOOTH SYNC method.");
        // 调用函数以获取当前时间
        obtain_time();
        // 更新当前时间
        time(&now);
    }
#endif

    char strftime_buf[64];  // 定义用于存储格式化时间字符串的缓冲区 
    // 设置时区为中国的标准时间（CST-8）
    setenv("TZ", "CST-8", 1);
    // 更新时区信息
    tzset();
    // 将当前时间转换为本地时间
    localtime_r(&now, &timeinfo);
    // 将时间信息格式化为字符串
    strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
    // 记录当前日期和时间的日志信息
    ESP_LOGI(TAG, "The current date/time in beijing is: %s", strftime_buf);

    time_info = parse_time_str(strftime_buf);

    // 获取SNTP同步模式
    uint8_t rest = sntp_get_sync_mode();
    // 打印同步模式的ID
    printf("mode_id: %d\r\n", rest);
    // 如果同步模式为平滑同步
    if (rest == SNTP_SYNC_MODE_SMOOTH) {
        struct timeval outdelta;  // 定义存储时间调整信息的结构体
        // 获取同步状态
        rest = sntp_get_sync_status();
        // 打印同步状态
        printf("sync status: %d\r\n", rest);
        // 当同步状态为同步进行中时，持续调整时间
        while (rest == SNTP_SYNC_STATUS_IN_PROGRESS) {
            // 调整时间并获取调整信息
            adjtime(NULL, &outdelta);
            // 记录时间调整的信息
            ESP_LOGI(TAG, "Waiting for adjusting time ... outdelta = %jd sec: %li ms: %li us",
                     (intmax_t)outdelta.tv_sec,
                     outdelta.tv_usec/1000,
                     outdelta.tv_usec%1000);
            // 延迟2秒后再次检查
            vTaskDelay(2000 / portTICK_PERIOD_MS);
        }
    }

    // 注释掉的代码用于设置ESP32进入深度睡眠
    // const int deep_sleep_sec = 10;
    // ESP_LOGI(TAG, "Entering deep sleep for %d seconds", deep_sleep_sec);
    // esp_deep_sleep(1000000LL * deep_sleep_sec);

    //设置标志位，以供下一个任务启动
    xEventGroupSetBits(xEventGroup, NTP_DONE_BIT);

    // 删除当前任务
    vTaskDelete(NULL);
}

 
static void print_servers(void)
{
    // 打印配置的NTP服务器列表
    ESP_LOGI(TAG, "List of configured NTP servers:");
    // 遍历最多SNTP_MAX_SERVERS个NTP服务器
    for (uint8_t i = 0; i < SNTP_MAX_SERVERS; ++i)
    {
        // 如果服务器有域名，则打印域名
        if (esp_sntp_getservername(i))
        {
            ESP_LOGI(TAG, "server %d: %s", i, esp_sntp_getservername(i));
        } 
        else 
        {
            // 否则，服务器有IPv4或IPv6地址，准备打印
            char buff[INET6_ADDRSTRLEN];
            // 定义一个足够大的缓冲区来存储IP地址字符串
            ip_addr_t const *ip = esp_sntp_getserver(i);
            // 获取服务器的IP地址,将IP地址转换为字符串并打印
            if (ipaddr_ntoa_r(ip, buff, INET6_ADDRSTRLEN) != NULL)
            ESP_LOGI(TAG, "server %d: %s", i, buff);
        }
    }
}


static void obtain_time(void)
{
    // 初始化网络接口，这是使用网络功能的基础步骤
    ESP_ERROR_CHECK(esp_netif_init());
 
#if LWIP_DHCP_GET_NTP_SRV 
    /**
     * 如果启用了通过DHCP获取NTP服务器地址的功能，此处进行SNTP的初始化配置。
     * NOTE: 在ESP获取到DHCP分配的IP地址之前调用此函数，否则NTP选项可能会被默认拒绝
     */
    ESP_LOGI(TAG, "Initializing SNTP");
    // 创建一个默认的SNTP配置结构体，最多配置3个NTP服务器，并手动指定了三个NTP服务器地址
    esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG_MULTIPLE(3, ESP_SNTP_SERVER_LIST("cn.pool.ntp.org", "time.windows.com", "ntp.sjtu.edu.cn"));
    config.start = false;                       // 设置为false，表示不立即启动SNTP服务，而是在连接完成后手动启动
    config.server_from_dhcp = true;             // 允许从DHCP服务器接收NTP地址，如果有的话
    config.renew_servers_after_new_IP = true;   // 在收到新的DHCP租赁时，允许esp-netif更新配置的NTP服务器
    config.index_of_first_server = 1;           // 从第1个服务器开始更新配置，保持第0个服务器（从DHCP获取）不变
 
    // 根据连接方式配置SNTP服务器更新的事件类型
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
    config.ip_event_to_renew = IP_EVENT_STA_GOT_IP;
#else
    config.ip_event_to_renew = IP_EVENT_ETH_GOT_IP;
#endif
    config.sync_cb = time_sync_notification_cb; // only if we need the notification function
    esp_netif_sntp_init(&config); // 根据配置初始化SNTP。
 
#endif /* LWIP_DHCP_GET_NTP_SRV */
 
#if LWIP_DHCP_GET_NTP_SRV
    ESP_LOGI(TAG, "Starting SNTP");
    esp_netif_sntp_start(); // 如果启用了通过DHCP获取NTP服务器地址的功能，则启动SNTP服务。
#endif
 
    print_servers(); // 打印信息
 
    // wait for time to be set
    time_t now = 0;
    struct tm timeinfo = { 0 };
    int retry = 0; // 定义了一个变量retry，用于记录已经尝试等待的时间同步的次数
    const int retry_count = 3;
    // 等待系统时间被设置，最多重试retry_coun次，每次等待。
    while (esp_netif_sntp_sync_wait(3000 / portTICK_PERIOD_MS) == ESP_ERR_TIMEOUT && ++retry < retry_count) {
        ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
    }
    time(&now);
    // 获取当前时间并将其转换为本地时间。
    localtime_r(&now, &timeinfo);
    // 检查时间有没有被设置
    if (timeinfo.tm_year >= (2016 - 1900)) 
    {
        ESP_LOGI(TAG, "Time is set: %s", asctime(&timeinfo));
    } 
    else 
    {
        ESP_LOGE(TAG, "Time is not set yet!");
    }

    // 停止并清理STNP服务
    esp_netif_sntp_deinit(); 
}






